<!DOCTYPE html>
<html lang="zh_cn">
<head>
          <title>来玩魔王的咚</title>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta charset="utf-8" />
        <!-- twitter card metadata -->
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="/images/mowang.png">
<meta name="twitter:site" content="">
<meta name="twitter:title" content="Docker 存储卷">
<meta name="twitter:description" content="<p>为何需要存储卷：数据存储的问题，Data volumes；在容器中使用volumes：Volumes types，Docker-managed volume，Bind-mount Volme，共享卷</p>">
        <!-- OG Tags -->
<meta property="og:url" content="/docker-volumes.html"/>
<meta property="og:title" content="来玩魔王的咚 | Docker 存储卷" />
<meta property="og:description" content="<p>为何需要存储卷：数据存储的问题，Data volumes；在容器中使用volumes：Volumes types，Docker-managed volume，Bind-mount Volme，共享卷</p>" />
        <!-- favicon -->
        <link rel="icon" type="image/png" href="/images/mowang.png">
        <!-- moment.js for date formatting -->
        <script src="/theme/js/moment.js"></script>
        <!-- css -->
        <link rel="stylesheet" type="text/css" href="/theme/css/main.css" />
        <!-- 左边的menu，如果页面高度不够，就跟着滚动，否则文章分类显示不全 -->
        <link rel="stylesheet" type="text/css" href="/theme/css/mycss/menu.css" />
		<script>
			
                /*! grunt-grunticon Stylesheet Loader - v2.1.2 | https://github.com/filamentgroup/grunticon | (c) 2015 Scott Jehl, Filament Group, Inc. | MIT license. */
    
    (function(e){function t(t,n,r,o){"use strict";function a(){for(var e,n=0;u.length>n;n++)u[n].href&&u[n].href.indexOf(t)>-1&&(e=!0);e?i.media=r||"all":setTimeout(a)}var i=e.document.createElement("link"),l=n||e.document.getElementsByTagName("script")[0],u=e.document.styleSheets;return i.rel="stylesheet",i.href=t,i.media="only x",i.onload=o||null,l.parentNode.insertBefore(i,l),a(),i}var n=function(r,o){"use strict";if(r&&3===r.length){var a=e.navigator,i=e.Image,l=!(!document.createElementNS||!document.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect||!document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image","1.1")||e.opera&&-1===a.userAgent.indexOf("Chrome")||-1!==a.userAgent.indexOf("Series40")),u=new i;u.onerror=function(){n.method="png",n.href=r[2],t(r[2])},u.onload=function(){var e=1===u.width&&1===u.height,a=r[e&&l?0:e?1:2];n.method=e&&l?"svg":e?"datapng":"png",n.href=a,t(a,null,null,o)},u.src="",document.documentElement.className+=" grunticon"}};n.loadCSS=t,e.grunticon=n})(this);(function(e,t){"use strict";var n=t.document,r="grunticon:",o=function(e){if(n.attachEvent?"complete"===n.readyState:"loading"!==n.readyState)e();else{var t=!1;n.addEventListener("readystatechange",function(){t||(t=!0,e())},!1)}},a=function(e){return t.document.querySelector('link[href$="'+e+'"]')},c=function(e){var t,n,o,a,c,i,u={};if(t=e.sheet,!t)return u;n=t.cssRules?t.cssRules:t.rules;for(var l=0;n.length>l;l++)o=n[l].cssText,a=r+n[l].selectorText,c=o.split(");")[0].match(/US\-ASCII\,([^"']+)/),c&&c[1]&&(i=decodeURIComponent(c[1]),u[a]=i);return u},i=function(e){var t,o,a;o="data-grunticon-embed";for(var c in e)if(a=c.slice(r.length),t=n.querySelectorAll(a+"["+o+"]"),t.length)for(var i=0;t.length>i;i++)t[i].innerHTML=e[c],t[i].style.backgroundImage="none",t[i].removeAttribute(o);return t},u=function(t){"svg"===e.method&&o(function(){i(c(a(e.href))),"function"==typeof t&&t()})};e.embedIcons=i,e.getCSS=a,e.getIcons=c,e.ready=o,e.svgLoadedCallback=u,e.embedSVG=u})(grunticon,this);
                
                grunticon(["/theme/css/icons.data.svg.css", "/theme/css/icons.data.png.css", "/theme/css/icons.fallback.css"]);
            </script>
        <noscript><link href="/theme/css/icons.fallback.css" rel="stylesheet"></noscript>
        <!-- menu toggle javascript -->
        <script type="text/javascript">
            document.addEventListener("DOMContentLoaded", initMenu);
            
            function initMenu(){
                var menu = document.getElementById("menu");
                var menulink = document.getElementById("menu-link");
                menulink.addEventListener("click", function toggleMenu(){
                        window.event.preventDefault();
                        menulink.classList.toggle('active');
                        menu.classList.toggle('active');              
                    });
            };
        </script>
        <!-- 不蒜子 -->
        <script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>

    <meta name="description" content="<p>为何需要存储卷：数据存储的问题，Data volumes；在容器中使用volumes：Volumes types，Docker-managed volume，Bind-mount Volme，共享卷</p>" />

    <meta name="tags" content="docker" />
  <!-- 替换部分base的样式，看文章时，要再宽一点，右边有很多空间可以撑开 -->
  <link rel="stylesheet" type="text/css" href="/theme/css/mycss/article.css" />

</head>
<body>
    <div role="banner" id="masthead">
        <header>
            <a href="/"><img src="/images/mowang.png" alt="McManus Logo"></a>
                <h1>来玩魔王的咚@骑士救兵</h1>
            <a href="#menu" id="menu-link">more stuff</a>
            <nav id="menu">
                <ul>
                        <li><a href="/tags">tags</a></li>
                            <li><a href="/category/cloud.html">Cloud</a></li>
                            <li class="active"><a href="/category/docker.html">Docker</a></li>
                            <li><a href="/category/go.html">Go</a></li>
                            <li><a href="/category/linux.html">Linux</a></li>
                            <li><a href="/category/python.html">Python</a></li>
                            <li><a href="/category/xue-xi-bi-ji.html">学习笔记</a></li>
                            <li><a href="/category/yun-wei-zi-dong-hua.html">运维自动化</a></li>
                </ul>
            </nav>
        </header>
    </div>
        <div class="page" role="main">
  <div class="article" role="article">
    <article>
        <footer>
            <a name="top"></a>
            <p>
              <time datetime=" 2020-07-22 14:00:00+08:00">
                <script>document.write(moment('2020-07-22 14:00:00+08:00').format('LL'));</script>
              </time>
              ~
              <time datetime=" 2020-07-22 14:00:00+08:00">
                <script>document.write(moment('2020-07-22 14:00:00+08:00').format('LL'));</script>
              </time>
            </p>
        </footer>
        <header>
          <h2>
            Docker 存储卷
          </h2>
        </header>
      <div class="content">
         <div class="toc">
<ul>
<li><a href="#wei-he-xu-yao-cun-chu-juan">为何需要存储卷</a><ul>
<li><a href="#shu-ju-cun-chu-de-wen-ti">数据存储的问题</a></li>
<li><a href="#data-volumes">Data volumes</a></li>
</ul>
</li>
<li><a href="#zai-rong-qi-zhong-shi-yong-volumes">在容器中使用volumes</a><ul>
<li><a href="#volumes-types">Volumes types</a></li>
<li><a href="#docker-managed-volume">Docker-managed volume</a></li>
<li><a href="#bind-mount-volme">Bind-mount Volme</a></li>
<li><a href="#gong-xiang-juan">共享卷</a></li>
</ul>
</li>
</ul>
</div>
<h3 id="wei-he-xu-yao-cun-chu-juan"><a class="toclink" href="#wei-he-xu-yao-cun-chu-juan">为何需要存储卷</a></h3>
<p>Docker镜像由多个只读层叠加而成，启动容器时，Docker会加载只读镜像层并在镜像栈顶部添加一个读写层。<br>
如果运行中的容器修改了现有的一个已经存在的文件，那该文件将会从读写层下面的只读层复制到读写层，该文件的只读版本仍然存在，只是已经被读写层中该文件的副本所隐藏，这个就是<strong>写时复制（COW）</strong>机制。  </p>
<h4 id="shu-ju-cun-chu-de-wen-ti"><a class="toclink" href="#shu-ju-cun-chu-de-wen-ti">数据存储的问题</a></h4>
<p>Docker 容器数据的存储，存在以下问题：</p>
<ul>
<li>存储于联合文件系统中，不易于宿主机访问</li>
<li>容器间数据共享不便</li>
<li>删除容器其数据会丢失</li>
</ul>
<p>关闭并重启容器，容器的数据不受影响。但删除容器会是之前的更改全部丢失。  </p>
<p>解决方案：卷（volume），或者便于区分叫做存储卷。<br>
卷是容器上的一个或多个目录，此类目录可绕过联合文件系统，与宿主机上的某个目录绑定（关联）。  </p>
<h4 id="data-volumes"><a class="toclink" href="#data-volumes">Data volumes</a></h4>
<p>Volume在容器初始化之时就会创建，由base image提供的卷中的数据会于此期间完成复制。  </p>
<p>Volumn的初衷就是独立于容器的生命周期实现数据持久化，因此删除容器之时既不会删除卷，也不会对哪怕未被引用的卷做垃圾回收操作。  </p>
<p>卷为docker提供了独立于容器的数据管理机制：</p>
<ul>
<li>可以把镜像想象成静态文件，例如程序。把卷类比为动态内容，例如数据。于是，镜像可以重用，而卷可以共享</li>
<li>卷实现了程序（镜像）和数据（卷）的分离，以及程序（镜像）和制作镜像的主机的分离。用户制作镜像时无须再考虑镜像运行的容器所在主机的环境。</li>
</ul>
<h3 id="zai-rong-qi-zhong-shi-yong-volumes"><a class="toclink" href="#zai-rong-qi-zhong-shi-yong-volumes">在容器中使用volumes</a></h3>
<p>现在，在创建容器时，加上volumes的相关选项。  </p>
<h4 id="volumes-types"><a class="toclink" href="#volumes-types">Volumes types</a></h4>
<p>Docker有两种类型的卷，每种类型都在容器中存在一个挂载点，但是在宿主机上的位置有所不同：</p>
<ul>
<li>Bind-mount volume: 容器内的目录和宿主机上的目录都需要指定</li>
<li>Docker-managed volume: 容器内的目录需要指定，宿主机上目录由docker维护</li>
</ul>
<p>为docker run命令使用-v选项，即可使用Volume。  </p>
<h4 id="docker-managed-volume"><a class="toclink" href="#docker-managed-volume">Docker-managed volume</a></h4>
<p>开一个会话创建容器并绑定volume：</p>
<div class="highlight"><pre><span></span><code>$ docker run --name v1 --rm -it -v /data busybox
</code></pre></div>

<h5 id="zai-su-zhu-ji-shang-cao-zuo-mu-lu"><a class="toclink" href="#zai-su-zhu-ji-shang-cao-zuo-mu-lu">在宿主机上操作目录</a></h5>
<p>在另一个会话中查询这个容器的inspect，不过内容比较多。在Volumes里能看到卷的信息：</p>
<div class="highlight"><pre><span></span><code>            <span class="s2">&quot;Volumes&quot;</span>: <span class="o">{</span>
                <span class="s2">&quot;/data&quot;</span>: <span class="o">{}</span>
            <span class="o">}</span>,
</code></pre></div>

<p>还有挂载点的信息，这里使用-f参数只看Mounts的内容：</p>
<div class="highlight"><pre><span></span><code>$ docker inspect -f <span class="s1">&#39;{{json .Mounts}}&#39;</span> v1
<span class="o">[{</span><span class="s2">&quot;Type&quot;</span>:<span class="s2">&quot;volume&quot;</span>,<span class="s2">&quot;Name&quot;</span>:<span class="s2">&quot;bdd48fb729e802b7d3a067da74b748037e83ff770a84f7215c657f6cc2af2c9d&quot;</span>,<span class="s2">&quot;Source&quot;</span>:<span class="s2">&quot;/var/lib/docker/volumes/bdd48fb729e802b7d3a067da74b748037e83ff770a84f7215c657f6cc2af2c9d/_data&quot;</span>,<span class="s2">&quot;Destination&quot;</span>:<span class="s2">&quot;/data&quot;</span>,<span class="s2">&quot;Driver&quot;</span>:<span class="s2">&quot;local&quot;</span>,<span class="s2">&quot;Mode&quot;</span>:<span class="s2">&quot;&quot;</span>,<span class="s2">&quot;RW&quot;</span>:true,<span class="s2">&quot;Propagation&quot;</span>:<span class="s2">&quot;&quot;</span><span class="o">}]</span>
$ 
</code></pre></div>

<p>宿主机自动分配的卷的路径在Source字段中，下面的方法直接获取路径并调用ls命令：</p>
<div class="highlight"><pre><span></span><code>$ docker inspect -f <span class="s1">&#39;{{range .Mounts}}{{.Source}} {{end}}&#39;</span> v1 <span class="p">|</span> xargs ls
$ docker inspect -f <span class="s1">&#39;{{with index .Mounts 0}}{{.Source}}{{end}}&#39;</span> v1 <span class="p">|</span> xargs ls
</code></pre></div>

<p>Mounts里是一个数组，可能有多个挂载点。第一行的命令是遍历所有的目录，第二行的命令是只输出第一个目录。<br>
在挂载点的目录中，容器和宿主机可以共享数据。容器中对目录的修改，在宿主机中可以查看到。反之，在宿主机中对这个目录的修改，容器也能够查看到。  </p>
<h5 id="shan-chu-jing-xiang"><a class="toclink" href="#shan-chu-jing-xiang">删除镜像</a></h5>
<p>上面创建容器的命令使用了--rm参数，这样一旦容器停止，该容器也就会自动被删除。容器删除后，Docker-managed volume也会一并被删除。<br>
所以这是一个临时的卷，卷内存储的数据依然会随着容器的删除而丢失。没有解决容器内数据持久保存的目的，但是现在容器的数据可以和宿主机共享，并且数据存储的I/O也直接是由宿主机的文件系统决定。好处是用户不用额外来管理这个卷，卷内存放的临时数据会随着容器的删除而删除。适合用于存放临时性的但是需要在容器外的共享的数据。  </p>
<h4 id="bind-mount-volme"><a class="toclink" href="#bind-mount-volme">Bind-mount Volme</a></h4>
<p>使用的参数还是和之前一样，但是-v参数的内容提供两部分，宿主机的目录和卷的目录，中间用冒号分隔：<code>-v HOSTDIR:VOLUMEDIR</code>。  </p>
<h5 id="bang-ding-bind-mount-volme"><a class="toclink" href="#bang-ding-bind-mount-volme">绑定 Bind-mount Volme</a></h5>
<p>开一个会话创建容器并绑定Bind-mount Volme：</p>
<div class="highlight"><pre><span></span><code>$ docker run --name v2 --rm -it -v ~/data/volumes/v2:/data busybox
</code></pre></div>

<p>仍然可以使用docker inspect命令来查看详细信息，这里就不演示了。<br>
这里指定的宿主机的路径如果不存在，docker会自动创建。  </p>
<h5 id="shan-chu-rong-qi"><a class="toclink" href="#shan-chu-rong-qi">删除容器</a></h5>
<p>容器删除后，宿主机上的文件会依然存在。这样就实现了数据在容器删除后依然可以使用。  </p>
<h4 id="gong-xiang-juan"><a class="toclink" href="#gong-xiang-juan">共享卷</a></h4>
<p>前面说了，docker有两种类型的卷，两种都已经说了。这里的共享卷本质上还是上面两种类型中的某一种，只是是实现了容器间共享同一个卷的两种方法。  </p>
<h5 id="duo-ge-rong-qi-juan-shi-yong-tong-yi-ge-zhu-ji-mu-lu"><a class="toclink" href="#duo-ge-rong-qi-juan-shi-yong-tong-yi-ge-zhu-ji-mu-lu">多个容器卷使用同一个主机目录</a></h5>
<p>这个没什么问题，主机上同一个目录是允许被多个容器同时使用的。  </p>
<h5 id="fu-zhi-shi-yong-qi-ta-rong-qi-de-juan"><a class="toclink" href="#fu-zhi-shi-yong-qi-ta-rong-qi-de-juan">复制使用其他容器的卷</a></h5>
<p>使用--volumes-from选项：</p>
<div class="highlight"><pre><span></span><code>$ docker run --name v3.0 --rm -itd -v /data busybox
e24397fffb44717a7f140010c829ec88f540ed7d95f7c8fb328a35a819becfb0
$ docker run --name v3.1 --rm -itd --volumes-from v3.0 busybox
28543eb6fc13e972a000ab955b9623630ebb9d90c8d3dc37a8b7608f24188926
$ docker container attach v3.0
/ <span class="c1"># touch /data/test_v3_0</span>
/ <span class="c1"># exit</span>
$ docker container attach v3.1
/ <span class="c1"># ls /data</span>
test_v3_0
/ <span class="c1"># exit</span>
$ 
</code></pre></div>

<p>这里的示例复制的是Docker-managed volume，对于Bind-mount Volme也是一样的。  </p>
<h5 id="gong-xiang-wang-luo"><a class="toclink" href="#gong-xiang-wang-luo">共享网络</a></h5>
<p>网络也可以共享，即联盟式网络。这里，卷也可以共享。在有些场景下，两个或几个容器有可能需要共享同一个网络并且共享同一个卷。  </p>
      </div>
      <div class="back-to-top">
        <a href="/">HOME</a>
        <a href="#top">TOP</a>
      </div>
    </article>
  </div>
<!-- end article -->
<!-- 页面往下滚动一段之后才会显示TOC -->
<script>
  window.onscroll = function() {
    var tocbox = document.getElementsByClassName('toc')[0];
    var osTop = document.documentElement.scrollTop || document.body.scrollTop;
    var osWidth = document.documentElement.scrollWidth || document.body.scrollWidth;
    // console.log(osTop)
    if (osTop>300 && osWidth>865) {
      tocbox.style.display = "block"
    }
    if (osTop<300 || osWidth<865) {
      tocbox.style.display = "none"
    }
  }
</script>
                <footer>
                    <div class="icons">
                    </div>
                    <span id="busuanzi_container_page_pv" style="padding: 10px">本文阅读量<span id="busuanzi_value_page_pv"></span>次</span>
                    <span id="busuanzi_container_site_pv" style="padding: 10px">本站总访问量<span id="busuanzi_value_site_pv"></span>次</span>
                    <span id="busuanzi_container_site_uv" style="padding: 10px">本站总访客数<span id="busuanzi_value_site_uv"></span>人</span>
                    <p>© <script>document.write(moment().format('YYYY'));</script> 749B</p>
                </footer>
        </div>
</body>
</html>